import org.codehaus.groovy.grails.plugins.springsecurity.Secured
//import org.springframework.security.providers.UsernamePasswordAuthenticationToken as AuthToken
//import org.springframework.security.context.SecurityContextHolder as SCH
import grails.converters.JSON
import com.st.utils.DataList

/**
 * User controller.
 */
@Secured(['ROLE_USER'])
class UserController extends AbstractSecurity {

    // the delete, save and update actions only accept POST requests
    static Map allowedMethods = [delete: 'POST', save: 'POST', update: 'POST']

    /** VIEWS **/
    def index = {redirect action:list}

    // List Views
    def list = {
        if (!isPowerUser()) {
            redirect action:show, id:getUserId()
            return
        }
    }

    // Show Views
    def show = {
        if (!isPowerUser() && (getUserId() != params.id)) {
            params.id = getUserId()
        }

        def user = User.get(params.id)

        if (user == null) {
            flash.message = "The user with the id $params.id was not found."
            redirect action:list
            return
        }
        return buildUserModel(user, false)
    }

    // Create/Edit Views
    // Not allow self registration yet.
    @Secured(['ROLE_ADMIN', 'ROLE_POWER_USER'])
    def create = {
        return buildUserModel(new User(person:new Person()), true)
    }

    def edit = {
        if (!isPowerUser() && (getUserId() != params.id)) {
            params.id = getUserId()
//            redirect action:edit, id:getUserId()
        }

        def user = User.get(params.id)
        if (!user) {
            flash.message = "The user with the id $params.id was not found."
            redirect action:list
            return
        }
        return buildUserModel(user, true)
    }

    /** ACTIONS **/
    // Save/Update/Delete Actions
    /**
     * User delete action. Before removing an existing user,
     * he should be removed from those roles which he is involved.
     */
    @Secured(['ROLE_ADMIN', 'ROLE_POWER_USER'])
    def delete = {
        def user = User.get(params.id)
        if (user) {
            def authPrincipal = authenticateService.principal()
            //avoid self-deletion if the logged-in user is an admin
            if (!(authPrincipal instanceof String) && authPrincipal.username == user.username) {
                flash.message = "You can not delete yourself, please login as another admin and try again"
            }
            else {
                //first, delete this user from role_users table.
                Role.findAll().each {it.removeFromUsers(user)}
                user.delete()
                flash.message = "User $params.id deleted."
            }
        }
        else {
            flash.message = "User not found with id $params.id"
        }

        redirect action:list
    }

    /**
     * User save action.
     */
    def save = {
        User.withTransaction {status->
            // BUG FIX
//            User.count()
            def person = new Person()
            person.properties = params
            def user = new User(person:person)
            user.properties = params

            if (!saveUser(user, 'create', 'user.saved.message')) {
                status.setRollbackOnly()
                flash.error = "There was a problem saving the User."
                render view:'create', model:buildUserModel(user, true)
                return
            }
            // We hae a valid user so we can add the roles.
            addRoles(user)
            redirect action:show, id:user.id
        }
    }

    /**
     * User update action.
     */
    def update = {
        def user = User.get(params.id)
        if (!user) {
            flash.message = "User with id $params.id was not found."
            redirect action:list
            return
        }

        long version = params.version.toLong()
        if (user.version > version) {
            user.errors.rejectValue 'version', "user.optimistic.locking.failure",
				"Another user has updated this User while you were editing."
            redirect action:edit, id:params.id
            return
        }

        User.withTransaction {status->
            user.properties = params
            user.person.properties = params

            if (!saveUser(user, 'edit', 'user.updated.message')) {
                status.setRollbackOnly()
                render view:'edit', model:buildUserModel(user)
                return
            }
            // We hae a valid user so we can add the roles.
            addRoles(user)
            redirect action:show, id:user.id
        }
    }

    /** JSON DATA **/
    def listUsersAsJSON = {
        // Getting the required values and setting up defaults for the default
        // people list table.
        def sortIndex = params.sidx ?: 'username'
        def sortOrder = params.sord ?: 'asc'
        def maxRows = Integer.valueOf(params.rows)
        def currentPage = Integer.valueOf(params.page) ?: 1
        def rowOffset = currentPage == 1 ? 0 : (currentPage - 1) * maxRows
        def searchValue = params.searchValue
        def users
        def count

        // Search for the Users.
        users = User.createCriteria().list(max:maxRows, offset:rowOffset) {
            // Searching for the criteria in the fields indicated below.
            // Using ilike to perform a case insensitive search.
            if (searchValue && searchValue.trim().length() > 1) {
                or {
                    ilike("username", "%${searchValue}%")
                    ilike("person.firstName", "%${searchValue}%")
                    ilike("person.lastName", "%${searchValue}%")
                    ilike("person.email", "%${searchValue}%")
                }
            }

            // Set the order.
            order(sortIndex, sortOrder).ignoreCase()
        }
        count = users.totalCount

        // Create the cells string to be converted to json later.
        def jsonCells = users.collect {
            [cell: [it.id,
                    it.username,
                    it.person.firstName,
                    it.person.lastName,
                    it.person.email,
                    it.enabled,
                    it.person.notes
                ],
                id: it.id]
        }

        // Create the rest of the json string to be used.
        def results = [rows:jsonCells, page:currentPage,
                       records:count,
                       total:Math.ceil(count / maxRows)]

        // If the results are null we set the results to "".
        if (!results) {
            results = ""
        }
        render results as JSON
    }

    /** UTILITIES **/
    private void addRoles(User user) {
        for (String key in params.keySet()) {
            if (key.startsWith('ROLE') &&
                    ('on' == params.get(key) || 'true' == params.get(key))) {
                Role.findByAuthority(key).addToUsers(user)
            }
        }
    }

    private Map buildUserModel(User user, boolean listAllRoles) {
        List roles = Role.list()
        roles.sort { r1, r2 ->
            r1.description <=> r2.description
        }
        LinkedHashMap<Role, Boolean> roleMap = [:]
        for (role in roles) {
            if (listAllRoles) {
                if (user.roles?.contains(role)) {
                    roleMap[(role)] = true
                } else {
                    roleMap[(role)] = false
                }
            } else {
                if (user.roles?.contains(role)) {
                    roleMap[(role)] = true
                }
            }
        }

        return [user:user, person:user.person, roleMap:roleMap]
    }

    private boolean saveUser(User user, String action, String messageCode) {
        if (user.validate()) {
            if (user.pass) {
                user.password = authenticateService.encodePassword(user.pass)
            }
            if (user.save(flush:true)) {
                flash.message = message(code:messageCode)
                return true
            }
        }
        return false
    }
}
